/*!
几个需要关注的配置或定义，目前简单粗暴，直接代码定义，没有做成配置文件方式
 * 根工作目录下的索引名称 const DEFUALT_INDEX_NAME:&str = "tantivy";
 * tantivy的写索引缓存大小 const DEFAULT_INDEX_MSIZE:usize = 1024*1024*5;

以下几个定义在define.rs中：
 * 文件扩展类型是否允许被Index索引，fn index_file_is_allow(ext: &str) -> bool
 * 文件扩展类型是否允许被在线查看内容（preview）fn is_preview_file(ext: &str) -> bool
 * 文件扩展类型是否是提供在线图片展示 fn is_image_file(ext: &str) -> bool

gfsc lib.rs中的重要定义：
 * const AUTH_TOKEN: &str = "hello";
 * pub const TOKEN_TIMEOUT: u64 = 60;

gfs支持2类文件的写入：：<B>存档类</B>和<B>Record类</B>
 * 存档类文件。此类文件以独立文件作为操作粒度，gfs严格进行name hash确定ihash, 并递增分配idx, 所以可以多次写入而不会覆盖。
 * 存档类有gfs stat记录。
 * record类文本行记录。此类文件每次写入的是文本行（记录）粒度，可以多次写入一个指定的文件，可以覆盖，也可以append。
 * record类文件不遵从hash原则，可以指定ihash、idx、name；也不自动Node内同步，如果需要同步，需要显式调用/node/sync with FileParam
 * record类文件不能覆盖存档类，但存档类可以覆盖record类。 
 * record类没有gfs stat记录，也没有其他系统记录。 
 
目录16进制好麻烦，10进制不更直观吗？ 
 * 节省长度空间
 * 是的，故意的。就是不让你轻松找到定位，请让系统来做（透明）！
 
 */
use anyhow::bail;
use axum::{
    error_handling::HandleErrorLayer,
    extract::{DefaultBodyLimit, Query, State},
    http::{StatusCode, Uri},
    response::Html,
    routing::{get, post, Router},
};
// use clap::{arg, command};
use sharecomm::ShareComm;
use std::borrow::Cow;
use std::io;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;

use tokio;
use tokio::signal;
use tower::{BoxError, ServiceBuilder};
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::trace::TraceLayer;
use axum::response::Result as AResult;
use axum::routing::get_service;
use std::env;
use tower_http::services::ServeDir;
use axum::extract::{BodyStream, Multipart, Path};
use rabbitmq::server as rmqserver;

#[cfg(feature = "tracelog")]
use tracing_appender::rolling::daily;
#[cfg(feature = "tracelog")]
use time::{format_description, UtcOffset};
#[cfg(feature = "tracelog")]
use tracing::{debug, error};
#[cfg(feature = "tracelog")]
use tracing_subscriber::{
    self, filter::EnvFilter, fmt, fmt::time::OffsetTime, layer::SubscriberExt,
    util::SubscriberInitExt, Registry,
};
#[cfg(feature = "rotatelog")]
use log::{debug, error, info, trace, warn};
#[cfg(feature = "rotatelog")]
use log4rs;
//use log::Level;

type Result<T> = anyhow::Result<T>;
mod demo;
mod web;
mod sharecomm;
mod worker;
//use web::*;
mod extract;
mod gfs;
mod grep;
mod stat;
mod consumer;
mod parallel;
mod server;
mod shutdown;

use demo::*;
use extract::*;
use gfs::*;
use gfsc::*;
use grep::*;
use stat::Stat;
use consumer::*;
use worker::Task;
use search::*;
use parallel::*;

type SharedHandle = Arc<ShareComm>;
/// 根工作目录下的索引名称
const DEFUALT_INDEX_NAME: &str = "tantivy";
/// tantivy的写索引缓存大小，因为每个文件都会commit，且RwLock进行了异步保护，太大意义不大。
const DEFAULT_INDEX_MSIZE: usize = 1024 * 1024 * 5;
///默认的MQ服务器配置项，表示没有QM，系统识别后将自动提高同步检测频率为5秒
const DEFAULT_FAST_SYNC_TIMER: u64 = 5;
const DEFAULT_SLOW_SYNC_TIMER: u64 = 10 * 60;
const DEFAULT_PORT: u16 = 7000;
//local mq is  ip:port  ; no is none ? redis is redis://:pass@ip:port/
const SYNC_CODE_RECORD:i32 = 255;

use clap::Parser;





#[cfg(feature = "tracelog")]
#[derive(Parser, Debug)]
#[clap(
    name = "rabbit",
    version,
    author = "lengss",
    about = "rabbit is a simple dfs base axum for everyone!"
)]
pub struct Cli {
    /// Sets a custom trace filter, such as info|debug|error|...
    #[clap(name = "trace", short('e'), long, default_value = "debug")]
    trace: String,
    /// Sets log path for tracing,  such as /tmp/rabbit, default no log
    #[clap(name = "log", short, long, default_value = "")]
    log: String,
    /// Secret for auth, default is token
    #[clap(name = "token",short, long, default_value = "token")]
    token: String,
    /// Master addr, http://ip:port, default null, means this is a master
    #[clap(name = "master", short, long, default_value = "")]
    master: String,
    ///  MQ server for sync, options are redis://:pwd@192.168.2.1:6379/, or 192.168.2.1:7099, default mean NO MQ!
    #[clap(name = "mq",short('q'), long, default_value = "localhost:7100")]
    mq: String,
    /// data path, default is ./root0 (root + node)"))
    #[clap(name = "data", short, long, default_value = "")]
    data: String,
    /// node id, more number for more gfs nodes
    #[clap(name = "node_id", short, long, default_value_t = 0)]
    node_id: u32,
    /// slave id, more slave more number. but master will ignore it (if current is master)
    #[clap(name = "slave_id", short, long, default_value_t = 1)]
    slave_id: u32,
    /// axum web server port
    #[clap(short, long, default_value_t = DEFAULT_PORT)]
    port: u16,
}

#[cfg(feature = "rotatelog")]
#[derive(Parser, Debug)]
#[clap(
    name = "rabbit",
    version,
    author = "lengss",
    about = "rabbit is a simple dfs base axum for everyone!"
)]
pub struct Cli {
    /// Sets rotatelog config, such as rotate.yaml, dafault(null) means hard-coding rotatelog at /tmp/rabbti/rabbit.log
    #[clap(name = "log", short, long, default_value = "")]
    trace: String,
    /// Secret for auth, default is token
    #[clap(name = "token",short, long, default_value = "token")]
    token: String,
    /// Master addr, http://ip:port, default null, means this is a master
    #[clap(name = "master", short, long, default_value = "")]
    master: String,
    ///  MQ server for sync, options are redis://:pwd@192.168.2.1:6379/, or 192.168.2.1:7100, default mean NO MQ!
    #[clap(name = "mq",short('q'), long, default_value = "localhost:7100")]
    mq: String,
    /// data path, default is ./root0 (root + node)"))
    #[clap(name = "data", short, long, default_value = "")]
    data: String,
    /// node id, more number for more gfs nodes
    #[clap(name = "node_id", short, long, default_value_t = 0)]
    node_id: u32,
    /// slave id, more slave more number. but master will ignore it (if current is master)
    #[clap(name = "slave_id", short, long, default_value_t = 1)]
    slave_id: u32,
    /// axum web server port
    #[clap(short, long, default_value_t = DEFAULT_PORT)]
    port: u16,
}
/**
tokio异步主程序入口
 */
#[tokio::main]
async fn main() -> Result<()> {
    let mut cli = Cli::parse();
   
    #[cfg(feature = "tracelog")]
        tracelog_init(&cli.trace, &cli.log);
    
    #[cfg(feature="rotatelog")]
        rotatelog_init(&cli.trace);    

    if cli.master.len() > 0 {
        if cli.master.starts_with("https://"){
            error!("{}, format is error", cli.master );
        }else if cli.master.starts_with("http://"){
        }else{ cli.master = format!("http://{}", &cli.master);  }
    }
    if cli.mq.len() > 0 {
        if cli.mq.starts_with("https://"){
            cli.mq = cli.mq.strip_prefix("https://").unwrap().to_string(); 
        }else if cli.mq.starts_with("http://"){
            cli.mq = cli.mq.strip_prefix("http://").unwrap().to_string();  
        }else{  }
    }

    if let Err(err) = server::startup(cli).await{
        error!("{:#?}", err);
    }
    Ok(())
}

/**
监听来自系统的ctrl_c和terminate信号.

- 这里会阻塞：
   tokio::select! {
       _ = ctrl_c => {},
       _ = terminate => {},
   }
- 此处会通过tokio::mspc::channel发送 shutdown 信号，让work线程优雅退出(gracefull shutdown)
   tx.send(Task::Cmd("***shutdown -h now ***".to_string()));
*/
async fn shutdown_signal(name: &str) {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }
    debug!("signal received, {} graceful shutdown", name);
}


#[cfg(feature = "tracelog")]
fn tracelog_init(trace_level: &str, log_path: &str) {

    debug!("当前采用tracing的rolling log模式, 可修改Cargo.toml, 改为基于log4rs的log模式");
    let secs = chrono::Local::now().offset().local_minus_utc();
    let offset = UtcOffset::from_whole_seconds(secs).unwrap();
    
    // 同时输出到文件和stderr
    let env_filter =
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(trace_level));    
    let format = "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]";    
    // 输出到（rolling）文件
    if log_path.len() > 0  {         // 写入log文件。
        let file_appender = daily( log_path, "rabbit.log");
        let formatting_layer = 
            fmt::layer()
            .pretty()
            .with_writer( file_appender)      
            .with_timer(OffsetTime::new(
                offset,
                format_description::parse(format).unwrap(),
            ));
            // 注册
        Registry::default()
            .with(env_filter)
            .with(formatting_layer)
            .init();    

    }else{  
        // 写入控制台 stderr
        let formatting_layer =       
            fmt::layer()
            .pretty()               
            .with_writer(std::io::stderr)
            .with_timer(OffsetTime::new(
                offset,
                format_description::parse(format).unwrap(),
            ));
// 注册
        Registry::default()
            .with(env_filter)
            .with(formatting_layer)
            .init();    
        };    
}

#[cfg(feature = "rotatelog")]
fn rotatelog_init(log_yaml: &str) {
    debug!("当前采用log4rs的log模式！");
    if log_yaml.ends_with(".yaml"){
        if let Err(err) = log4rs::init_file(log_yaml, Default::default()) {
            println!("{:#?}", err);
        }
    }else{
        rotatelog_default();
    }    
}

#[cfg(feature = "rotatelog")]
fn rotatelog_default() {
    let config_str = include_str!("../../logrotate.yaml");
    let config = serde_yaml::from_str(config_str);
    if let Err(err) = config {
        println!("load config file is error! {:#?}", err);
        return 
    }
    let r = log4rs::init_raw_config(config.unwrap());
    if let Err(err) = r {
        println!("logrotate setting is error! {:#?}", err);
        return 
    }
}

#[cfg(feature = "tracelog")]
pub const fn loglevel(level :log::Level)-> tracing::Level {
    match level{
        log::Level::Debug => tracing::Level::DEBUG,
        log::Level::Info => tracing::Level::INFO,
        log::Level::Error => tracing::Level::ERROR,
        log::Level::Warn => tracing::Level::WARN,
        log::Level::Trace => tracing::Level::TRACE,
    }    
}
#[cfg(feature = "rotatelog")]
pub const fn loglevel(level: log::Level)-> log::Level {
    level
}
